/*
 * Copyright (c) 2009, Jan Pastor. All Rights Reserved.
 */

package sk.jp.commons.utils.xml;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.w3c.dom.Document;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import sk.jp.commons.utils.FileUtil;

/**
 * Utility class for validation XML document.
 *
 * @author Jan Pastor 
 */
public class XmlValidator {

    private XmlValidator(){
    }

    //----------------------------------------------------------------------

    /**
     * Validates the <code>document</code> with schema located by specified
     * <code>schemaFileName</code>.
     * @param document to validate.
     * @param schemaFileName path and file name of schema.
     * @param charsetName charset used for read XSD file
     * (if null, the <code>system default</code> charset is used).
     * @throws ValidationException if validation fails.
     */
    public static void validate(Document document, String schemaFileName,
                                String charsetName)
                            throws ValidationException, IOException {
        File schemaFile = new File(schemaFileName);
        if(!schemaFile.canRead()){
            throw new IOException("Can't read schema file ("+schemaFileName+")");
        }

        FileInputStream fis = new FileInputStream(schemaFile);
        byte[] data = FileUtil.getBytesFromStream(fis);
        String schema;
        if(charsetName != null){
            schema = new String(data, charsetName);
        }else{
            schema = new String(data);
        }
        //
        try {
            validateDoc(document, schema);
        } catch (Exception e) {
            throw new ValidationException(ValidationException.ERROR,
                                          "Document is not valid (schema file: '"+
                                          schemaFileName+"')", e);
        }
    }

    /**
     * Validates the <code>document</code> with schema located by specified
     * <code>schemaFileName</code>.
     * @param document to validate.
     * @param xsd XSD schema.
     * @throws ValidationException if validation fails.
     */
    public static void validateDoc(Document document, String xsd)
                            throws ValidationException {
        Validator validator;
        try {
            // create a SchemaFactory capable of understanding WXS schemas:
            SchemaFactory factory = SchemaFactory.newInstance(
                                                XMLConstants.W3C_XML_SCHEMA_NS_URI);
            // load a WXS schema, represented by a Schema instance:
            Source schemaSource = new StreamSource(new StringReader(xsd));
            Schema schema = factory.newSchema(schemaSource);
            // create a Validator instance, which can be used to validate an
            // instance document:
            validator = schema.newValidator();
            validator.setErrorHandler(new ErrorHandler(){
                public void fatalError(SAXParseException e) throws SAXException {
                    throw new ValidationException(ValidationException.FATAL_ERROR,
                                                  "Document is not valid", e);
                }
                public void error(SAXParseException e) throws SAXException {
                    throw new ValidationException(ValidationException.ERROR,
                                                  "Document is not valid", e);
                }
                public void warning(SAXParseException e) throws SAXException {
                    throw new ValidationException(ValidationException.WARNING,
                                                  "Document is valid but warning occurrs", e);
                }
            });
        } catch (Exception e) {
            throw new RuntimeException("Validation failed", e);
        }
        // validate the DOM tree:
        try {
            validator.validate(new DOMSource(document));
        } catch (IOException e) {
            throw new ValidationException(ValidationException.ERROR,
                                          "Validation failed", e);
        } catch (SAXException e) {
            throw new ValidationException(ValidationException.ERROR,
                                          "Validation failed", e);
        }
    }

}
